/**
 * 
 */
package com.cqcis.uip.utils.client;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 接口平台客户端模拟. 模拟socket客户端，进行对服务端的测试 包含标准的编解码模拟, 二进制发送模拟，并发测试模拟
 * 
 * @author huangmy
 * @date 2008-2-22
 */
public class UipSocketClientSimulation {
	private static String ARG_BEGIN = "-D";
	
	private static String ARG_PACKAGE = "p";
	private static String ARG_CRM_PACKAGE = "cp"; // crm方式补全包头包尾
	private static String ARG_DIR = "dir"; // 目录中的bak文件
	private static String ARG_DF = "df"; // 目录内的xml文件
	private static String ARG_MT = "mt"; // multithread 多线程
	private static String ARG_CHECK = "-Dcheck"; // 包校验信息

	public static void main(String[] args) throws Exception {
		if (args != null && "-help".equals(args[0])) {
			// 帮助信息
			log("用法 (java -jar socketclient.jar ip port path [args...]); \n" +
					"\t-help 获取帮助\n" +
					"\t-Dcp  -- crm方式补全包头包尾\n" +
					"\t-Ddir -- 目录中的bak文件\n" +
					"\t-Ddf  -- 目录内的xml文件, 各文件名以 数字+'.xml'方式命名,从1开始编号,如1.xml \n" +
					"\t-Dmt  -- 多线程发送目录中的bak文件,并发发送,与-Ddir选项配合使用\n" +
					"\t-Dcheck -- 包校验信息 (发送系统ID + 接收系统ID + 认证密码)\n");
			return;
		}
		
		if (args == null || args.length < 3) {
			log("参数错误: 用法 (java -jar socketclient.jar ip port path [args...]); -help 获取帮助");

			return;
		}

		UipAddress uipAddress = new UipAddress(args[0], Integer.parseInt(args[1]), args[2]);
		
		for (int i = 0; i < args.length; i ++) {
			String arg = args[i];
			if (arg.indexOf(ARG_BEGIN) >= 0) {
				parseArgRet(arg, uipAddress);
			}
			
			if (arg.equals(ARG_CHECK)) {
				uipAddress.setCheckStr(args[i + 1]);
			}
		}
				
		log(uipAddress);
		
		UipSocketClientSimulation client = new UipSocketClientSimulation();
		client.start(uipAddress);
		
	}
	
	public void start(UipAddress uipAddress) throws Exception {
		doBinary(uipAddress);
	}
	
	private static void parseArgRet(String str, UipAddress uipAddress) {
		int begin = str.indexOf(ARG_BEGIN);
		if (begin < 0) {
			return;
		}
		
		String arg = str.substring(ARG_BEGIN.length());
		
		if (arg == null || arg.length() <= 0) {
			return;
		}
		
		// 解析各个标志控制
		if (arg.equals(ARG_PACKAGE)) {   
			uipAddress.setPakcage(true);
		} else if (arg.equals(ARG_CRM_PACKAGE)) {
			uipAddress.setWeakPackage(true);
		} else if (arg.equals(ARG_DIR)) {
			uipAddress.setDirPath(true);
		} else if (arg.equals(ARG_DF)) {
			uipAddress.setDirPath(true);
			uipAddress.setDirPathFile(true);
		} else if (arg.equals(ARG_MT)) {
			uipAddress.setMultiThread(true);
		}
	}
	
	/**
	 * 发送数据且接收
	 * 
	 * @param file
	 * @param uipAddress
	 * @throws Exception
	 */
	private boolean doSendAndRecv(File file, UipAddress uipAddress, AtomicLong totalServerDealTime, boolean multied) throws Exception {
        byte[] buf = new byte[1024]; // buffer
		
		Socket client = new Socket();

		client.connect(new InetSocketAddress(uipAddress.getAddress(),
				uipAddress.getPort()));
		
		log("连接到 " + uipAddress.getAddress() + ":" + uipAddress.getPort() + " 成功.");
		log("正在发送数据......");
        		
		OutputStream os = client.getOutputStream();
				
		// 增加包头
		if (uipAddress.isWeakPackage()) {
			os.write(new byte[]{0x01, 0x02});
			
			if (uipAddress.getCheckStr() != null) {
				os.write(uipAddress.getCheckStr().getBytes());
			} else {
				os.write("RM9711110000".getBytes());				
			}
			
			os.write(new byte[]{0x00, 0x00});
			
			if (!uipAddress.isDirPathFile()) {
				int fileLength = (int)file.length();
				
				byte[] lenbuf = new byte[4];
				for (int i = 0; i < 4; i ++) {				
					lenbuf[3 - i] = (byte)fileLength;
					fileLength = fileLength >> 8;
				}
				
				os.write(lenbuf);				
			}			
		}
		
		// 单独的文件
		if (uipAddress.isDirPathFile()) {
			// 串起来打包
			File[] xmlFiles = file.listFiles(new FilenameFilter() {
				public boolean accept(File dir, String name) {
					if (name.endsWith(".xml")) {
						return true;
					}
					return false;
				}
			});
			
			int maybeLen = 0;			
			for (int j = 1; j <= xmlFiles.length; j ++) {
				File xmlFile = new File(file.getCanonicalPath() + "/" + j + ".xml");
				maybeLen += xmlFile.length() + 10;
			}
			
			byte[] sendBuffer = new byte[maybeLen * 4];			
			int sendBufferLength = 4;			
			for (int j = 1; j <= xmlFiles.length; j ++) {
				File xmlFile = new File(file.getCanonicalPath() + "/" + j + ".xml");
				// 包头
				System.arraycopy(new byte[]{0x01, 0x02}, 0, sendBuffer, sendBufferLength, 2);
				System.arraycopy(new byte[]{0x00, 0x00}, 0, sendBuffer, sendBufferLength + 2, 2);
				
				// 内容
				byte[] xmlBuf = new byte[(int)xmlFile.length() * 2];
				byte[] xmBufTemp = new byte[1024];
				InputStream isXml = new FileInputStream(xmlFile);
				int bufLength = 0;
				int count = 0;
				while ((count = isXml.read(xmBufTemp)) > 0) {
					System.arraycopy(xmBufTemp, 0, xmlBuf, bufLength, count);
					bufLength += count;
				}
				
				isXml.close();
				
                String xmlStr = new String(xmlBuf,0,bufLength);							
				xmlBuf = xmlStr.getBytes("UTF-8");
				
                int fileLength = xmlBuf.length;
                
				byte[] lenbuf = new byte[4];
				for (int i = 0; i < 4; i ++) {				
					lenbuf[3 - i] = (byte)fileLength;
					fileLength = fileLength >> 8;
				}
				
				System.arraycopy(lenbuf, 0, sendBuffer, sendBufferLength + 2 + 2, lenbuf.length);
				
				System.arraycopy(xmlBuf, 0, sendBuffer, sendBufferLength + 2 + 2 + 4, xmlBuf.length);
				
				// 包尾
				System.arraycopy(new byte[]{(byte)0xfe, (byte)0xff}, 0, sendBuffer, sendBufferLength + 2 + 2 + 4 + xmlBuf.length, 2);
								
				sendBufferLength += 10 + xmlBuf.length;
			}
			
			int totalLen = sendBufferLength - 4;
			byte[] lenbufHead = new byte[4];
			for (int i = 0; i < 4; i ++) {
				lenbufHead[3 - i] = (byte)totalLen;
				totalLen = totalLen >> 8;
			}
			
			System.arraycopy(lenbufHead, 0, sendBuffer, 0, 4);
			os.write(sendBuffer, 0, sendBufferLength);			
		} else {
			InputStream is = new FileInputStream(file);
			int count = 0;
			while ((count = is.read(buf)) > 0) {			
				os.write(buf, 0, count);
			}
			is.close();
		}
		
		// 增加包尾
		if (uipAddress.isWeakPackage()) {
			os.write(new byte[]{(byte)0xfe, (byte)0xff});			
		}
				
		log("等待返回数据......");
		
		long beginTime = System.currentTimeMillis();
		boolean ret = false;
		try {
			InputStream sis = new BufferedInputStream(client.getInputStream());
			OutputStream stdoutOs = System.err;

			log("返回数据 = ");			
			int countRecv = 0;
			while ((countRecv = sis.read(buf)) > 0) {
				ret = true;
				stdoutOs.write(buf, 0, countRecv);

				if (sis.available() <= 0) {
					break;
				}
			}

			os.close();
			sis.close();
		} finally {
			long endTime = System.currentTimeMillis();
			
			// 总花费时间
			long totalTime = endTime - beginTime;
			synchronized (totalServerDealTime) {
				if (multied) {
					long time = totalServerDealTime.get();
					if (time == 0 || time < totalTime) {
						totalServerDealTime.set(totalTime);
					}
				} else {
					totalServerDealTime.addAndGet(totalTime);
				}
			}
			
			if (ret) {				
				log("接收返回数据成功.");
				log("服务端处理时间 = " + totalTime/1000 + "." 
						+ totalTime%1000 + " 秒.");			
			} else {
				log("接收返回数据失败... 服务端关闭连接.");
				return false;
			}
		}
		
		return true;
	}
    
	/**
	 * 直接发送二进制流
	 * 
	 * @param uipAddress
	 * @throws Exception
	 */
	private void doBinary(final UipAddress uipAddress) throws Exception {
		final LinkedList<File> sendfiles = new LinkedList<File>();
		int sendfilesNum = 0;
		final Object lock = new Object();
		
		// get file
		File file = new File(uipAddress.getPath());
		if (!file.isFile() && !uipAddress.isDirPath()) {
			throw new Exception("指定路径不是一个文件. " + uipAddress.getPath());
		}
		// 如果是目录
		else if (uipAddress.isDirPath()) {
			 if (!file.isDirectory()) {
				 throw new Exception("指定路径不是一个目录. " + uipAddress.getPath());
			 }
			 
			 if (!uipAddress.isDirPathFile()) {
				File[] bakFiles = file.listFiles();
				if (bakFiles != null) {
					for (File bakFile : bakFiles) {
						sendfiles.add(bakFile);
						sendfilesNum ++;
					}
				}
			} else {
				sendfiles.add(file);
				sendfilesNum ++;
			}
		} 
		else {
			sendfiles.add(file);
			sendfilesNum ++;
		}
		
		final List<File> successFiles = Collections.synchronizedList(new ArrayList<File>()); // 成功数
		final List<File> errorFiles = Collections.synchronizedList(new ArrayList<File>()); // 失败数
		
		ThreadPoolExecutor tpt = new ThreadPoolExecutor(sendfiles.size(), sendfiles.size(), 60,
				TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
		
		int totalFileNum = sendfiles.size();
		
		final AtomicLong totalServerDealTime = new AtomicLong(); // 服务端总处理时间
		
		final CountDownLatch latch = new CountDownLatch(totalFileNum);
		
		if (uipAddress.isMultiThread()) { // 在独立的线程中并发发送			
			for (int i = 0; i < totalFileNum; i ++) {
				tpt.execute(new Runnable() {
					public void run() {
						File sendFile = null;
						synchronized (sendfiles) {
							sendFile = sendfiles.removeFirst();
						}
						
						if (sendFile != null) {
							try {
								boolean b = doSendAndRecv(sendFile, uipAddress, totalServerDealTime, true);
								if (b) {
									successFiles.add(sendFile);
								} else {
									errorFiles.add(sendFile);
								}
							} catch (Exception e) {
								errorFiles.add(sendFile);
								try {
									log("发送文件异常: " + sendFile.getCanonicalPath(), e);
								} catch (IOException e1) {								
								}
							} finally {
								latch.countDown();
							}
						}
						
						synchronized (lock) {
							lock.notify();
						}
					}
				});
			}
		} else {
			for (File sendFile : sendfiles) {
				try {
					boolean b = doSendAndRecv(sendFile, uipAddress, totalServerDealTime, false);
					if (b) {
						successFiles.add(sendFile);
					} else {
						errorFiles.add(sendFile);
					}
				} catch (Exception e) {
					errorFiles.add(sendFile);
					log("发送文件异常: " + sendFile.getCanonicalPath(), e);
				}
			}
		}
		
		if (uipAddress.isMultiThread()) {
			latch.await();						
			tpt.shutdownNow();
		}
		
		log("总发送文件数: " + totalFileNum + ", 成功数: " + successFiles.size()
				+ ", 失败数: " + errorFiles.size());
		long totalTime = totalServerDealTime.longValue();
		log("总处理时间 = " + totalTime/1000 + "." 
				+ totalTime%1000 + " 秒.");
		if (errorFiles.size() > 0) {
			log("失败文件: ");
			for (File errFile : errorFiles) {
				log(errFile.getCanonicalPath());
			}
		}		
	}
	
	private static void log(Object message) {
		log(message, null);
	}

	private static void log(Object message, Throwable t) {		
		String messageTotal = null;
		
		if (message != null) {
			if (t != null) {
				message = message + "\n";
			}			
			messageTotal = message.toString();
		}

		if (t != null) {
			StringWriter sw = new StringWriter();
			t.printStackTrace(new PrintWriter(sw));

			if (messageTotal != null) {
				messageTotal += sw;
			} else {
				messageTotal = sw.toString();
			}
		}

		if (messageTotal != null) {
			System.err.print("\n[INFO] " + messageTotal);
		}
	}
}
